package com.whately.core.redis.distributelock;

import com.whately.core.base.utils.IpUtils;
import com.whately.core.base.model.RedisKeyBuilder;
import com.whately.core.redis.utils.RedisCacheStringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;

/**
 * 描述: 注解 的Aop
 */
@Component
@Aspect
@Slf4j
public class RedisLockMethodLockAspect {
    @Autowired
    private RedisLockRegistry redisLockRegistry;

    @Autowired
    private RedisCacheStringUtils redisCacheStringUtils;

    @Pointcut("@annotation(com.whately.core.redis.distributelock.WhatelyDistributedLock)")
    private void apiAop(){

    }

    @Around("apiAop()")
    public Object aroundApi(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        String signatureName = signature.getName();
        Method method = signature.getMethod();
        WhatelyDistributedLock lockDistributed = method.getAnnotation(WhatelyDistributedLock.class);
        String localRegistryKey = lockDistributed.lockKey();
        if(StringUtils.isBlank(localRegistryKey)){
            localRegistryKey = point.getTarget().getClass().getSimpleName()+"-"+method.getName();
            log.warn("localRegistry value  be null ,use signatureName and method name as localRegistryKey {}",localRegistryKey);
        }

        String dsCode = lockDistributed.dsCode();
        /*if(StringUtils.isBlank(dsCode)){
            dsCode = DataSourceMapFactory.getDefaultDsCode();
        }*/

        if(StringUtils.isNotBlank(dsCode)){
            localRegistryKey  += ("-"+dsCode);
        }

        //重设redisKey的保持时间
        int durationRedisKey = lockDistributed.keyDurationSecondsInRedis();

        RedisKeyBuilder rkb = RedisKeyBuilder.newInstance();
        if(StringUtils.isNotBlank(dsCode)){
            rkb.appendVarWithMH(dsCode);
        }
        rkb.appendVarWithMH("whately-self-lock");
        rkb.appendVar(localRegistryKey);

        log.info("signatureName {},method name {},localRegistryKey {},try lock wait flag {},durationRedisKey {}",
                signatureName,method.getName(),localRegistryKey,lockDistributed.waitFlag(),durationRedisKey);

        Lock lock = redisLockRegistry.obtain(localRegistryKey);
        boolean b = false;
        int retry = lockDistributed.waitLockRetryCount();
        if(retry<=0){
            retry=1;
        }

        for(int i =0 ; i<retry;i++){
            if(lockDistributed.waitFlag()){
                b = lock.tryLock(lockDistributed.waitLockTime(), TimeUnit.SECONDS);
            }else{
                b = lock.tryLock();
            }

            if(b){
                break;
            }else {
                continue;
            }
        }
        log.info("locked flag is {}  ,lock is {} ,lockDistributed obj info {}",b,lock,lockDistributed);

        if(!b){
            log.error("localRegistry get lock fail , task return , signatureName {},method name {}",signatureName,method.getName());
            throw new RuntimeException("localRegistry get lock fail , task return !!!!!! ");
        }

        Object proceed = null;
        try{
            //通过redis再一次控制
            boolean selfNxLock = true;
            if(durationRedisKey>0){
                selfNxLock =redisCacheStringUtils.setNx(rkb,1+"-"+ IpUtils.getLocalHostIp(),durationRedisKey, TimeUnit.SECONDS);
            }

            if(!selfNxLock){
                //自定义加锁失败
                log.error("localRegistry selfNxLock failed , task return , signatureName {},method name {}",signatureName,method.getName());
                throw new RuntimeException("localRegistry selfNxLock failed , task return !!!!!! ");
            }
            //开始执行
            proceed = point.proceed();
        }catch (Exception e){
            log.error("localRegistry error ",e);
            throw e;
        }finally {
            log.info("unlock begin {}",lock);
            try{
                lock.unlock();
            }catch (Exception e){
                log.error(e.getMessage(),e);
            }
        }


        return proceed;

    }

}
